/*
 * Flashlib - QB9 common Flash library
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.qb9.flashlib.utils
{
	import com.qb9.flashlib.color.Color;
	
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.InteractiveObject;
	import flash.display.Loader;
	import flash.display.MovieClip;
	import flash.display.Shape;
	import flash.geom.ColorTransform;
	import flash.geom.Point;
	
	/** Utility functions to operate on DisplayObject instances. */
	public class DisplayUtil
	{
		/**
		 * Removes all the elements from a DisplayObjectContainer.
		 * @param elem The element.
		 */
		public static function empty(elem:DisplayObjectContainer) : void
		{
			var i:int = elem.numChildren;
			while (i--)
				elem.removeChildAt(0);
		}
		
		/**
		 * Returns the descendant given a sequence of names separated by '.';
		 * Equivalent to calling getChildByName(name) recursively until reaching the target.
		 * @param elem The container.
		 * @param path The target path.
		 * @return The target element
		 */
		public static function getByPath(elem:DisplayObjectContainer, path:String = ''):DisplayObject
		{
			var child:DisplayObject = elem;
			for each (var name:String in path.split('.'))
				if (name && child is DisplayObjectContainer)
					child = DisplayObjectContainer(child).getChildByName(name);

			return child;
		}
		
		/**
		 * Returns the (first) element by that name, inside container.
		 * Equivalent to container.getChildByName(name) but this one is recursive.
		 * @param elem The element.
		 * @param name The desired name.
		 * @return The first found element or null
		 */
		public static function getByName(elem:DisplayObjectContainer, name:String):DisplayObject
		{
			var child:DisplayObject = elem.getChildByName(name);
			var i:int = elem.numChildren;
			
			// Won't get into the loop when really found
			while (!child && i--)
			{
				var sub:DisplayObjectContainer = elem.getChildAt(i) as DisplayObjectContainer;
				if (sub) 
					child = getByName(sub, name);
			}
			
			return child;
		}
		
		/**
		 * Returns an array with all the descendants by certain name
		 * @param elem The element.
		 * @param name The desired name.
		 */
		public static function getAllByName(elem:DisplayObjectContainer, name:String):Array
		{
			var matching:Array = [];
			for each (var child:DisplayObject in children(elem, true))
				if (child.name === name)
					matching.push(child);
			return matching;
		}
		
		/**
		 * Calls MovieClip.stop() to a MovieClip 
		 * @param obj The displayObject.
		 */
		public static function stopMovieClip(obj:DisplayObject):void
		{
			if (obj is MovieClip) 
				MovieClip(obj).stop();
		}
		
		/**
		 * Returns an array with the element's children or descendants (if recursive)
		 * @param elem The element.
		 * @param recursive If true, the function continues recursively (first children then parents).
		 * @return A regular array with the children.
		 */
		public static function children(elem:DisplayObjectContainer, recursive:Boolean = false):Array
		{
			var list:Array = [];
			for (var i:int = 0, l:int = elem.numChildren; i < l; i++)
			{
				var child:DisplayObject = elem.getChildAt(i);
				// This is one is odd but happens every now and then
				if (!child)
					continue;
				
				if (recursive && child is DisplayObjectContainer)
				{
					var descendants:Array = children(child as DisplayObjectContainer, true);
					ArrayUtil.append(list, descendants);
				}

				list.push(child);
			}

			return list;
		}
		
		/**
		 * Removes an element, won't fail if it's doesn't have parent.
		 * @param elem The element.
		 */
		public static function remove(elem:DisplayObject) : void
		{
			stopMovieClip(elem);
			if (elem.parent && !(elem.parent is Loader))
				elem.parent.removeChild(elem);
		}
		
		/**
		 * Hides an element, mainly useful as callback for another function.
		 * For example: foreach(children(elem), hide);
		 * @param elem The element.
		 */
		public static function hide(elem:DisplayObject) : void
		{
			elem.visible = false;
		}
		
		/**
		 * Shows an element, mainly useful as callback for another function.
		 * @param elem The element.
		 */
		public static function show(elem:DisplayObject) : void
		{
			elem.visible = true;
		}
		
		/**
		 * Returns the x offset in relation to another object that contains it.
		 * @param elem The target element.
		 * @param relativeTo The related container or null for the Main.
		 * @return The x position. 
		 */
		public static function offsetX(elem:DisplayObject, relativeTo:DisplayObject=null) : Number
		{
			var x:Number = 0;
			while (elem)
			{
				x += elem.x;
				elem = elem.parent;
			}
			
			if (relativeTo)
				x -= offsetX(relativeTo);
			return x;
		}
		
		/**
		 * Returns the y offset in relation to another object that contains it.
		 * @param elem The target element.
		 * @param relativeTo The related container or null for the Main.
		 * @return The y position.
		 */
		public static function offsetY(elem:DisplayObject, relativeTo:DisplayObject=null) : Number
		{
			var y:Number = 0;
			while (elem)
			{
				y += elem.y;
				elem = elem.parent;
			}
			
			if (relativeTo)
				y -= offsetY(relativeTo);

			return y;
		}
		
		/**
		 * Returns a point with the global coordinates of a display object
		 * @param elem The target element.
		 */
		public static function globalCoords(elem:DisplayObject):Point
		{
			return new Point(offsetX(elem), offsetY(elem));
		}
		
		/**
		 * Sets the x and y properties of an element to their global values
		 * <b>Call prior to moving it from its natural container</b>
		 * @param elem The target element.
		 */
		public static function globalize(elem:DisplayObject):void
		{
			var global:Point = globalCoords(elem);
			elem.x = global.x;
			elem.y = global.y;
		}
		
		/**
		 * Returns a point with the local coordinates of a previously globalized display object
		 * @param elem The target element.
		 */
		public static function localCoords(elem:DisplayObject) : Point
		{
			return new Point(elem.x,elem.y).subtract(globalCoords(elem.parent)); 
		}
		
		public static function reparentTo(elem:DisplayObject, newParent:DisplayObjectContainer):void
		{
			globalize(elem);
			newParent.addChild(elem);
			localize(elem);
		}

		/**
		 * Sets the x and y properties of an element to their local values
		 * <b>Call after moving it to its final container</b>
		 * @param elem The target element.
		 */
		public static function localize(elem:DisplayObject) : void
		{
			var local:Point = localCoords(elem);
			elem.x = local.x;
			elem.y = local.y;
		}
		
		/**
		 * Adds a mask to a display object
		 * @param elem The target element.
		 * @param sx Starting X coordinate of the mask
		 * @param sy Starting Y coordinate of the mask
		 * @param ex Ending X coordinate of the mask
		 * @param ey Ending Y coordinate of the mask
		 * 
		 * <p>
		 * 	<b>Note:</b> 
		 * 	Starting coordinates are relative to the top-left corner of the element.
		 *  Ending coordinates are relative to the bottom-right corner of the element.
		 * </p>
		 */
		public static function mask(elem:DisplayObject, sx:Number = 0, sy:Number = 0, ex:Number = 0, ey:Number = 0) : Shape
		{
			var mask:Shape = new Shape();
			mask.graphics.beginFill(Color.BLACK);
			mask.graphics.drawRect(sx, sy, elem.width + ex, elem.height + ey);
			mask.x = elem.x;
			mask.y = elem.y;
			elem.parent.addChild(mask);
			elem.mask = mask;
			return mask;
		}
		
		/**
		 * Centers a DisplayObject
		 * <b>Must be inserted into another element</b>
		 * @param elem The target element.
		 */
		public static function center(elem:DisplayObject) : void
		{
			with(elem)
			{
				if (parent)
				{
					x = (parent.width - width) / 2;
					y = (parent.height - height) / 2;
				}
			}
		}
		
		/**
		 * Puts a DisplayObject on top of all its siblings
		 * <b>Must be inserted into another element</b>
		 * @param elem The target element.
		 */
		public static function bringToFront(elem:DisplayObject) : void
		{
			var p:DisplayObjectContainer = elem.parent;
			if (p)
				p.setChildIndex(elem, p.numChildren-1);
		}

		/**
		 * Recursively clean a DisplayObject.
		 */
		public static function dispose(elem:DisplayObject):void
		{
			var container:DisplayObjectContainer = elem as DisplayObjectContainer;
			if (container)
				for each (var child:DisplayObject in children(container, true))
					stopMovieClip(child);
			
			stopMovieClip(elem);
			remove(elem);
		}
		
		/**
		 * Fully tints DisplayObject.
		 */
		public static function tint(elem:DisplayObject, hex:uint):void
		{
			var transform:ColorTransform = new ColorTransform();
			transform.color = hex;
			elem.transform.colorTransform = transform;
		}
		
		/**
		 * Creates a color transform that tints by multiplying
		 */
		public static function createTransform(hex:uint, brightness:Number=1):ColorTransform
		{
			var color:Color = new Color(hex);
			var max:uint = 0xFF / brightness;
			
			return new ColorTransform(
				color.r / max,
				color.g / max,
				color.b / max
			);
		}

		/**
		 * Sets a color transform that tints by multiplying
		 */
		public static function transparentTint(elem:DisplayObject, hex:uint, brightness:Number=1):void
		{
			elem.transform.colorTransform = createTransform(hex, brightness);
		}

		/** Disables the mouse */
		public static function disableMouse(elem:DisplayObject):void
		{
			if (elem is InteractiveObject)
			{
				InteractiveObject(elem).mouseEnabled = false;
				if (elem is DisplayObjectContainer)
					DisplayObjectContainer(elem).mouseChildren = false;
			}
		}
	}
}
